iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
自我挑戰組

Eric 的創作者重生計畫系列 第 19

DAY 19. Vibe Coding 記錄—大量文字轉檔、分割

  • 分享至 

  • xImage
  •  

我在把文字送進 NotebookLM 時,有時會遇到格式需要轉檔,或是一份檔案文字太多,需要分割的問題,這時該怎麼辦呢?

在 Google Colab 環境下運行的 Python 程式碼。它會將上傳的巨大純文字檔案(.txt),依照 NotebookLM 的 50 萬字上限(安全起見,預設設為 45 萬字,避免特殊字元計算差異)進行精準分割,並自動打包成一個 ZIP 檔提供下載。

在 Colab 中貼上並執行以下程式碼即可:

import os
import shutil
from google.colab import files

def split_text_file(input_file_path, max_chars=450000, output_dir='split_files'):
    """
    將純文字檔案依照字數限制分割,並存入指定資料夾。
    """
    # 建立輸出資料夾,若已存在則先刪除以確保乾淨
    if os.path.exists(output_dir):
        shutil.rmtree(output_dir)
    os.makedirs(output_dir)
    
    print(f"📖 開始讀取檔案:{input_file_path} ...")
    with open(input_file_path, 'r', encoding='utf-8') as f:
        text = f.read()
        
    total_chars = len(text)
    print(f"📊 檔案總字數:{total_chars} 字")
    
    # 開始分割
    part_num = 1
    start_idx = 0
    
    base_name = os.path.splitext(os.path.basename(input_file_path))[0]
    
    while start_idx < total_chars:
        end_idx = start_idx + max_chars
        chunk = text[start_idx:end_idx]
        
        output_filename = f"{base_name}_part{part_num}.txt"
        output_path = os.path.join(output_dir, output_filename)
        
        with open(output_path, 'w', encoding='utf-8') as f_out:
            f_out.write(chunk)
            
        print(f"💾 已生成:{output_filename} ({len(chunk)} 字)")
        
        part_num += 1
        start_idx = end_idx

    print(f"✅ 所有片段分割完成,共 {part_num - 1} 個檔案。")
    return output_dir

def main():
    # 1. 提示使用者上傳檔案
    print("請上傳你要分割的 .txt 檔案:")
    uploaded = files.upload()
    
    if not uploaded:
        print("❌ 未上傳任何檔案。")
        return
        
    # 取得上傳的檔名
    input_file_path = list(uploaded.keys())[0]
    
    # 2. 執行分割 (NotebookLM 上限 50 萬字,這裡抓 45 萬字安全牌)
    output_folder = split_text_file(input_file_path, max_chars=450000)
    
    # 3. 打包成 ZIP
    zip_filename = f"{os.path.splitext(input_file_path)[0]}_split_archive"
    print(f"📦 正在打包成 {zip_filename}.zip ...")
    shutil.make_archive(zip_filename, 'zip', output_folder)
    
    # 4. 下載 ZIP 檔案
    print("📥 準備下載...")
    files.download(f"{zip_filename}.zip")
    print("🎉 下載已觸發!")

if __name__ == "__main__":
    main()


💡 程式碼特點與注意事項

  • 字數安全邊際: 雖然 NotebookLM 上限是 50 萬字,但有時候空格、換行符號(\n\r\n)或特殊 Unicode 字元在不同系統計算下會有微小差距。因此程式碼中將 max_chars 設為 450,000 字,留點扣達最安全。
  • 關於 200 MB 限制: 純文字(UTF-8)格式下,45 萬字的大小大約只有 1 MB 到 1.5 MB 左右,所以只要卡緊「字數限制」,檔案大小絕對不可能超過 200 MB,大可完全放心。

之後我在這個網站上看到支援格式,我又手癢了,

幫我寫python,大量html、epub、純文字,轉換成md,整合成完整單一壓縮文件,google colab環境

使用 Python 的 pathlib 處理路徑、 zipfile 進行壓縮,並利用 beautifulsoup4 處理 HTML,以及 ebooklib 來精準解析 EPUB 檔案。

1. 安裝必要套件

Colab 預設沒有安裝處理 EPUB 必備的 ebooklib ,先執行此儲存格:

!pip install ebooklib beautifulsoup4 tqdm

2. 核心轉換腳本

這個腳本會自動建立輸入與輸出資料夾。你只需要 將大量 HTML、EPUB 和 TXT 檔案上傳到 input_files 資料夾 ,執行後就會在根目錄得到一個 converted_markdown.zip 壓縮檔。

import os
import re
import zipfile
from pathlib import Path
import ebooklib
from ebooklib import epub
from bs4 import BeautifulSoup
from tqdm import tqdm

# ==========================================
# 設定路徑
# ==========================================
INPUT_DIR = Path('./input_files')
OUTPUT_DIR = Path('./output_markdown')
ZIP_OUTPUT_PATH = Path('./converted_markdown.zip')

# 建立必要資料夾
INPUT_DIR.mkdir(parents=True, exist_ok=True)
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

# ==========================================
# 轉換核心函式
# ==========================================

def clean_html_to_md(html_content):
    """將 HTML 結構轉為基礎的 Markdown 格式"""
    if not html_content.strip():
        return ""
    
    soup = BeautifulSoup(html_content, 'html.parser')
    
    # 移除不必要的標籤
    for script in soup(["script", "style", "meta", "noscript"]):
        script.decompose()
        
    # 簡單的 HTML 轉 MD 規則
    for h in soup.find_all(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']):
        level = int(h.name[1])
        h.replace_with(f"\n\n{'#' * level} {h.get_text().strip()}\n\n")
        
    for p in soup.find_all('p'):
        p.replace_with(f"\n{p.get_text().strip()}\n")
        
    for br in soup.find_all('br'):
        br.replace_with("\n")
        
    for li in soup.find_all('li'):
        li.replace_with(f"\n* {li.get_text().strip()}")

    # 取得純文字並清理連續換行
    text = soup.get_text()
    text = re.sub(r'\n{3,}', '\n\n', text)
    return text.strip()

def convert_txt(file_path):
    """讀取純文字"""
    try:
        return file_path.read_text(encoding='utf-8')
    except UnicodeDecodeError:
        # 備用編碼嘗試
        return file_path.read_text(encoding='big5', errors='ignore')

def convert_html(file_path):
    """讀取並轉換 HTML"""
    try:
        content = file_path.read_text(encoding='utf-8')
    except UnicodeDecodeError:
        content = file_path.read_text(encoding='big5', errors='ignore')
    return clean_html_to_md(content)

def convert_epub(file_path):
    """解析 EPUB 並將內部章節轉為 Markdown"""
    md_content = []
    try:
        # ignore_ncx 減少警告訊息
        book = epub.read_epub(file_path, options={'ignore_ncx': True})
        
        # 取得書名作為大標題
        title = book.get_metadata('DC', 'title')
        if title:
            md_content.append(# {title[0][0]}\n\n")
            
        # 遍歷書籍內部元件
        for item in book.get_items():
            if item.get_type() == ebooklib.ITEM_DOCUMENT:
                html_bytes = item.get_content()
                html_str = html_bytes.decode('utf-8', errors='ignore')
                chapter_md = clean_html_to_md(html_str)
                if chapter_md:
                    md_content.append(chapter_md)
                    md_content.append("\n\n---\n\n") # 章節分隔線
                    
        return "".join(md_content)
    except Exception as e:
        print(f"解析 EPUB 失敗 [{file_path.name}]: {str(e)}")
        return ""

# ==========================================
# 主執行流程
# ==========================================

def main():
    # 支援的副檔名對照表
    extension_map = {
        '.txt': convert_txt,
        '.html': convert_html,
        '.htm': convert_html,
        '.epub': convert_epub
    }
    
    # 搜集所有檔案
    all_files = [f for f in INPUT_DIR.rglob('*') if f.suffix.lower() in extension_map]
    
    if not all_files:
        print(f"⚠️  在 '{INPUT_DIR}' 資料夾中沒有找到支援的檔案 (.txt, .html, .epub)!")
        print("請點擊 Colab 左側的資料夾圖示,將檔案上傳到該目錄下再重新執行。")
        return

    print(f"📂 找到 {len(all_files)} 個檔案,開始轉換...")
    
    # 開始批次轉換
    for file_path in tqdm(all_files, desc="轉換進度"):
        suffix = file_path.suffix.lower()
        convert_func = extension_map[suffix]
        
        # 進行轉換
        md_text = convert_func(file_path)
        
        if md_text.strip():
            # 保持原本的分層結構,並改副檔名為 .md
            relative_path = file_path.relative_to(INPUT_DIR)
            output_file_path = OUTPUT_DIR / relative_path.with_suffix('.md')
            
            # 自動建立子資料夾
            output_file_path.parent.mkdir(parents=True, exist_ok=True)
            output_file_path.write_text(md_text, encoding='utf-8')

    # 打包成單一壓縮檔
    print("🤐 正在將所有 Markdown 檔案打包壓縮...")
    with zipfile.ZipFile(ZIP_OUTPUT_PATH, 'w', zipfile.ZIP_DEFLATED) as zipf:
        for root, dirs, files in os.walk(OUTPUT_DIR):
            for file in files:
                file_path = Path(root) / file
                # 讓壓縮檔內的目錄結構從 output 內部開始算
                archive_name = file_path.relative_to(OUTPUT_DIR)
                zipf.write(file_path, archive_name)
                
    print(f"🎉 完成!整合壓縮檔已產生:{ZIP_OUTPUT_PATH.resolve()}")
    print("你可以點擊左側檔案面板中的 'converted_markdown.zip' 右鍵進行下載。")

if __name__ == '__main__':
    main()

最後 Eric 說:
現在 AI 真的實在方便了,只要有一點基本觀念足矣,剩下就是下提示詞,接著就能得到想要的工具了。


上一篇
DAY 18. 我的小說速讀神器:怠惰小說+NotebookLM
下一篇
DAY 20. 我目前的寫作工具與我的心流✏️
系列文
Eric 的創作者重生計畫21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言